#include "Base/AsyncLogger.h"
#include "Base/Timestamp.h"
#include "Base/CurrectProcess.h"
#include <unistd.h>
#include <cstdio>

namespace toym
{

// std::mutex AsyncLogger::s_SingletonMutex;
// std::atomic<AsyncLogger*> AsyncLogger::s_pInstance(nullptr);

AsyncLogger::AsyncLogger()
    : m_curBuf(new LargeBuffer),
      m_nextBuf(new LargeBuffer),
      m_buffers(),
      m_mutex(),
      m_cond(),
      m_interval(3),
      m_outputFile(),
      m_logFileSizeLimit(4UL*1024UL*1024UL*1024UL),
      m_thread(nullptr)
{
    setProcessName("logfile");
    ::gethostname(m_hostName, sizeof(m_hostName));
}

void AsyncLogger::putLog(const char* log, std::size_t sz) {
    AsyncLogger::getInstance()->putLogNonstatic(log, sz);
}

void AsyncLogger::putLogNonstatic(const char* log, std::size_t sz) {
    std::unique_lock<std::mutex> lock(m_mutex);
    if (m_curBuf->availableSize() >= sz) {
        // 足够
        m_curBuf->append(log, sz);
    } else {
        // 当前的不够
        m_buffers.push_back(std::move(m_curBuf));
        if (m_nextBuf) {
            // 存在另一个buffer
            m_curBuf = std::move(m_nextBuf);
        } else {
            m_curBuf.reset(new LargeBuffer);    
        }
        m_curBuf->append(log, sz);

        // 有要输出的了，唤醒
        m_cond.notify_one();
    }
}

void AsyncLogger::flushLog() {
    AsyncLogger::getInstance()->flushLogNonstatic();
}

void AsyncLogger::flushLogNonstatic() {
    std::unique_lock<std::mutex> lock(m_mutex);
    if (m_curBuf->availableSize() > 0) {
        m_buffers.push_back(std::move(m_curBuf));
        if (m_nextBuf) {
            m_curBuf = std::move(m_nextBuf);
        } else {
            m_curBuf.reset(new LargeBuffer);
        }
        m_cond.notify_one();
    }
}

void AsyncLogger::setProcessName(const char* name) {
    int i = 0;
    int limitSize = sizeof(m_processName) - 1;
    for (i = 0; i < limitSize && name[i] != '\0'; ++i)
        m_processName[i] = name[i];
    m_processName[i] = '\0';
}

void AsyncLogger::startThread() {
    if (m_thread == nullptr) {
        m_thread = new std::thread(&AsyncLogger::threadFunc, this);
    }
}

void AsyncLogger::threadFunc() {
    BufferPtr newBuffer1(new LargeBuffer);
    BufferPtr newBuffer2(new LargeBuffer);
    BufferVertor vtr;

    for (;;) {
        assert(vtr.size() == 0);
        {
            std::unique_lock<std::mutex> lock(m_mutex);
            if (m_buffers.empty())
                m_cond.wait_for(lock, m_interval);
            m_buffers.push_back(std::move(m_curBuf));
            m_buffers.swap(vtr);
            m_curBuf.swap(newBuffer1);
            if (!m_nextBuf) {
                m_nextBuf.swap(newBuffer2);
            }
        }
        writeToFile(vtr);
        std::size_t curIdx = 0;
        if (vtr.size() > curIdx && !newBuffer1) {
            newBuffer1.swap(vtr[curIdx++]);
        }
        if (vtr.size() > curIdx && !newBuffer2) {
            newBuffer2.swap(vtr[curIdx]);
        }
        vtr.clear();
        if (!newBuffer1) {
            newBuffer1.reset(new LargeBuffer);
        }
        if (!newBuffer2) {
            newBuffer2.reset(new LargeBuffer);
        }
    }
}

void AsyncLogger::writeToFile(BufferVertor& bv) {
    if (m_outputFile.data() == nullptr) {
        m_outputFile.openWrite(getLogFileName());
    }
    if (m_outputFile.fileSize() >= m_logFileSizeLimit) {
        m_outputFile.openWrite(getLogFileName());
    }
    for (auto& pb: bv) {
        if (pb->used() > 0) {
            m_outputFile.write(pb->data(), pb->used());
            pb->clear();
        }   
    }
    m_outputFile.flush();
}

const char* AsyncLogger::getLogFileName() {
    Timestamp now = Timestamp::now();
    // std::int64_t second = now.getTick() / Timestamp::s_tickPerSecond;
    now.toStructTm(&m_tm);
    std::snprintf(m_logFileName, 
                  sizeof(m_logFileName), 
                  "%s.%d%02d%02d-%02d%02d%02d.%s.%s.log",
                  m_processName,
                  m_tm.tm_year+1900,
                  m_tm.tm_mon + 1,
                  m_tm.tm_mday,
                  m_tm.tm_hour,
                  m_tm.tm_min,
                  m_tm.tm_sec,
                  m_hostName,
                  CurrectProcess::getPidStr()
                  );
    return m_logFileName;
}

} // namespace toym
